package intelligentcity.model.traffic;

import intelligentcity.conf.Conf;
import intelligentcity.memoirs.Diary;
import intelligentcity.model.Move;
import intelligentcity.model.Point;
import intelligentcity.model.Road;
import intelligentcity.model.intersections.Intersection;
import intelligentcity.model.streets.OneWayStreet;
import intelligentcity.system.LightColor;
import intelligentcity.system.Lights;

import java.util.Random;

public class BehaviourOnIntersection extends Behaviour {

    enum Option {
        GO_STRAIGHT, TURN_RIGHT, TURN_LEFT
    }

    private final static Random random = new Random();

    private int counterOfPointOfTurnsAtIntersection;
    private Option option;

    public BehaviourOnIntersection(Car car) {
        super(car);

        counterOfPointOfTurnsAtIntersection = 0;
        int optionNumber = random.nextInt(Conf.maxNumberOfPointOfTurnsAtIntersection + 1);
        // int optionNumber = 2; // Option.TURN_LEFT;
        switch (optionNumber) {
            case 0:
                option = Option.GO_STRAIGHT;
                break;
            case 1:
                option = Option.TURN_RIGHT;
                break;
            case 2:
                option = Option.TURN_LEFT;
                car.setWillingToTurnLeft(true);
                break;
        }

        writeToDiaryOnEntry();
        waitForGreenLight();
    }

    @Override
    public void behave() {

        for (Point pointOfTurn : currentRoad.getPointsOfTurn()) {
            if (point.equals(pointOfTurn)) {
                currentOWS = selectStreet();
                car.setCurrentOWS(currentOWS);
                if (counterOfPointOfTurnsAtIntersection == 2 && Option.TURN_LEFT.equals(option)) {
                    letThroughCarsFromTheOppositeSide();
                    car.setWillingToTurnLeft(false);
                }

                break;
            }
        }

    }

    private OneWayStreet getOWSofCurrentPoint(Road r, Move move) {
        for (OneWayStreet ows : currentRoad.getOneWayStreets()) {
            if (ows.contains(point) && move.equals(ows.getMove())) {
                return ows;
            }
        }
        return null;
    }

    private OneWayStreet selectStreet() {

        counterOfPointOfTurnsAtIntersection++;
        Move currentMove = currentOWS.getMove();
        if (Option.GO_STRAIGHT.equals(option)) {
            return currentOWS;
        } else if (Option.TURN_RIGHT.equals(option) && counterOfPointOfTurnsAtIntersection == 1) {

            switch (currentMove) {
                case UP: {
                    return getOWSofCurrentPoint(currentRoad, Move.RIGHT);
                }
                case DOWN: {
                    return getOWSofCurrentPoint(currentRoad, Move.LEFT);
                }
                case LEFT: {
                    return getOWSofCurrentPoint(currentRoad, Move.UP);
                }
                case RIGHT: {
                    return getOWSofCurrentPoint(currentRoad, Move.DOWN);
                }
            }

        } else if (Option.TURN_LEFT.equals(option) && counterOfPointOfTurnsAtIntersection == 2) {

            switch (currentMove) {
                case UP: {
                    return getOWSofCurrentPoint(currentRoad, Move.LEFT);
                }
                case DOWN: {
                    return getOWSofCurrentPoint(currentRoad, Move.RIGHT);
                }
                case LEFT: {
                    return getOWSofCurrentPoint(currentRoad, Move.DOWN);
                }
                case RIGHT: {
                    return getOWSofCurrentPoint(currentRoad, Move.UP);
                }
            }
        }

        Conf.stop(Conf.carStopTimeWhileStreetSelecting);
        return currentOWS;

    }

    private void waitForGreenLight() {
        Lights lights = ((Intersection) currentRoad).getLights();
        switch (currentOWS.getMove()) {
            case UP: {
                while (lights.getSouth() != LightColor.GREEN) {
                    Conf.stop(Conf.carStopTimeBetweenColourOfLightChecking);
                }
                break;
            }
            case DOWN: {
                while (lights.getNorth() != LightColor.GREEN) {
                    Conf.stop(Conf.carStopTimeBetweenColourOfLightChecking);
                }
                break;
            }
            case LEFT: {
                while (lights.getEast() != LightColor.GREEN) {
                    Conf.stop(Conf.carStopTimeBetweenColourOfLightChecking);
                }
                break;
            }
            case RIGHT: {
                while (lights.getWest() != LightColor.GREEN) {
                    Conf.stop(Conf.carStopTimeBetweenColourOfLightChecking);
                }
                break;
            }

        }
    }

    private void writeToDiaryOnEntry() {
        Diary diary = ((Intersection) currentRoad).getDiary();
        switch (currentOWS.getMove()) {

            case UP:
                diary.incEntrySouthOnCurrentPage();
                break;

            case DOWN:
                diary.incEntryNorthOnCurrentPage();
                break;

            case LEFT:
                diary.incEntryEastOnCurrentPage();
                break;

            case RIGHT:
                diary.incEntryWestOnCurrentPage();
                break;
        }
    }

    private void letThroughCarsFromTheOppositeSide() {

        try {
            switch (currentOWS.getMove()) {
                case UP: {
                    boolean canIMove = false;
                    while (!canIMove) {
                        boolean shouldILet = false;
                        for (Integer i : traffic.getCars().keySet()) {
                            Point anotherCar = traffic.getCars().get(i).getPoint();
                            if (anotherCar.getY() == point.getY() - Conf.distanceBeetweenStreets
                                    && point.getX() <= anotherCar.getX()
                                    && anotherCar.getX() < point.getX() + Conf.safeDistance) { // NullPointerException
                                Conf.stop(Conf.whileTime);
                                shouldILet = true;
                                break;
                            }

                        }
                        if (shouldILet == true) {
                            letThroughCarsFromTheOppositeSide();
                        } else {
                            canIMove = true;
                        }
                    }
                    break;
                }
                case DOWN: {

                    boolean canIMove = false;
                    while (!canIMove) {
                        boolean shouldILet = false;
                        for (Integer i : traffic.getCars().keySet()) {
                            Point anotherCar = traffic.getCars().get(i).getPoint();
                            if (anotherCar.getY() == point.getY() + Conf.distanceBeetweenStreets
                                    && point.getX() - Conf.safeDistance < anotherCar.getX()
                                    && anotherCar.getX() < point.getX()) { // NullPointerException
                                Conf.stop(Conf.whileTime);
                                shouldILet = true;
                                break;
                            }

                        }
                        if (shouldILet == true) {
                            letThroughCarsFromTheOppositeSide();
                        } else {
                            canIMove = true;
                        }
                    }
                    break;
                }
                case LEFT: {
                    boolean canIMove = false;
                    while (!canIMove) {
                        boolean shouldILet = false;
                        for (Integer i : traffic.getCars().keySet()) {
                            Point anotherCar = traffic.getCars().get(i).getPoint();
                            if (anotherCar.getX() == point.getX() - Conf.distanceBeetweenStreets
                                    && point.getY() >= anotherCar.getY()
                                    && anotherCar.getY() > point.getY() - Conf.safeDistance) { // NullPointerException
                                Conf.stop(Conf.whileTime);
                                shouldILet = true;
                                break;
                            }

                        }
                        if (shouldILet == true) {
                            letThroughCarsFromTheOppositeSide();
                        } else {
                            canIMove = true;
                        }
                    }
                    break;
                }
                case RIGHT: {
                    boolean canIMove = false;
                    while (!canIMove) {
                        boolean shouldILet = false;
                        for (Integer i : traffic.getCars().keySet()) {
                            Point anotherCar = traffic.getCars().get(i).getPoint();
                            if (anotherCar.getX() == point.getX() + Conf.distanceBeetweenStreets
                                    && point.getY() <= anotherCar.getY()
                                    && anotherCar.getY() < point.getY() + Conf.safeDistance) { // NullPointerException
                                Conf.stop(Conf.whileTime);
                                shouldILet = true;
                                break;
                            }

                        }
                        if (shouldILet == true) {
                            letThroughCarsFromTheOppositeSide();
                        } else {
                            canIMove = true;
                        }
                    }
                    break;
                }
            }
        } catch (NullPointerException e) {
            letThroughCarsFromTheOppositeSide();
        }
    }

}
